﻿#region Disclaimer/Info
///////////////////////////////////////////////////////////////////////////////////////////////////
// MetaCollection
// 
// MetaCollection is a system to provide a standard structure, permissions and metadata service
// for content organization.
//
// This project is hosted at Google Code at http://code.google.com/p/metacollection/
//
// This project is licensed under the BSD license.  See the License.txt file for more information.
///////////////////////////////////////////////////////////////////////////////////////////////////
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using MetaCollection.Framework;
using MetaCollection.Framework.Model;
using Model = MetaCollection.Framework.Model;
using MetaCollection.ContentService.Http;
using System.Xml.Linq;
using System.Xml;
using System.Xml.Serialization;

namespace MetaCollection.ContentService.WebDav.Methods
{
	/// <summary>
	/// A REPORT request is an extensible mechanism for obtaining information about a resource. Unlike a resource property, which has a single value, the value of a report can depend on additional information specified in the REPORT request body and in the REPORT request headers.
	/// 
	/// Marshalling:
	/// 
	///     The body of a REPORT request specifies which report is being requested, as well as any additional information that will be used to customize the report.
	///     The request MAY include a Depth header. If no Depth header is included, Depth:0 is assumed.
	///     The response body for a successful request MUST contain the requested report.
	///     If a Depth request header is included, the response MUST be a 207 Multi-Status. The request MUST be applied separately to the collection itself and to all members of the collection that satisfy the Depth value. The DAV:prop element of a DAV:response for a given resource MUST contain the requested report for that resource.
	/// 
	/// Preconditions:
	/// 
	///     (DAV:supported-report): The specified report MUST be supported by the resource identified by the request-URL.
	/// 
	/// Postconditions:
	/// 
	///     (DAV:no-modification): The REPORT method MUST NOT have changed the content or dead properties of any resource.
	///
	/// </summary>
	/// <example>
	/*
	 * Request
	 * 
		<?xml version="1.0" encoding="utf-8" ?>
		<D:version-tree xmlns:D="DAV:">
			<D:prop>
				<D:version-name/>
				<D:creator-displayname/>
				<D:predecessor-set/>
			</D:prop>
		</D:version-tree>
	 * 
	 *  Response
	 *  
		<?xml version="1.0" encoding="utf-8"?>
		<D:multistatus xmlns:D="DAV:" xmlns:mc="uri://MetaCollection/v2" xmlns:Z="urn:schemas-microsoft-com:" xmlns:Office="urn:schemas-microsoft-com:office:office" xmlns:Repl="http://schemas.microsoft.com/repl/">
			<D:response xmlns:mcs="uri://MetaCollection/v2/spec/61cf794a-3045-4ed4-92cb-4986af37d007">
				<D:href>/Ops?ver=1</D:href>
				<D:propstat>
					<D:prop>
						<D:version-name>V1</D:version-name>
						<D:creator-displayname>System</D:creator-displayname>
					</D:prop>
					<D:status>HTTP/1.1 200 OK</D:status>
				</D:propstat>
			</D:response>
			<D:response xmlns:mcs="uri://MetaCollection/v2/spec/61cf794a-3045-4ed4-92cb-4986af37d007">
				<D:href>/Ops?ver=2</D:href>
				<D:propstat>
					<D:prop>
						<D:version-name>V2</D:version-name>
						<D:creator-displayname>NT AUTHORITY\IUSR</D:creator-displayname>
					</D:prop>
					<D:status>HTTP/1.1 200 OK</D:status>
				</D:propstat>
			</D:response>
			<D:response xmlns:mcs="uri://MetaCollection/v2/spec/61cf794a-3045-4ed4-92cb-4986af37d007">
				<D:href>/Ops?ver=3</D:href>
				<D:propstat>
					<D:prop>
						<D:version-name>V3</D:version-name>
						<D:creator-displayname>NT AUTHORITY\IUSR</D:creator-displayname>
					</D:prop>
					<D:status>HTTP/1.1 200 OK</D:status>
				</D:propstat>
			</D:response>
		</D:multistatus>
	 */
	/// </example>
	public static class Report
	{
		public static void Respond(DavContext context, IRequestHandler handler)
		{
			IEnumerable<IItemInstance> itemVersions =
				handler.ItemRequest(context.User, RequestTypes.GetItem | RequestTypes.VersionAll | RequestTypes.ProcessSingleItem, context.Item.Href, Model.LoadOptions.ItemComplete);

			XElement requestXml = context.Request.Xml;
			ReportRequestType reportType = GetRequestType(requestXml);
			List<XName> requestedProperties = GetRequestedProperties(requestXml);
			IEnumerable<XElement> responseData;
			context.Response.SetCacheHeaders(HttpCacheability.ServerAndPrivate, DateTime.Now);
			switch (reportType)
			{
				case ReportRequestType.VersionTree:
					responseData = itemVersions.DavData(Properties.LoadOptionsFromRequestedProperties(requestedProperties)).PatchHrefWithVersion().PropInfoResponse(context, requestedProperties);
					break;
				case ReportRequestType.ResourceProviders:
					responseData = ProviderReferenceReport(context.Item);
					break;
				case ReportRequestType.SpecificationDetail:
					responseData = SpecificationDetails(requestXml);
					break;
				default: throw new Http.ServerException(ServerErrorStatus.NotImplemented);
			}
			context.Response.OutputXml(responseData);
		}

		private static List<XElement> SpecificationDetails(XElement requestXml)
		{
			List<XElement> elementList = new List<XElement>();
			var providerId = requestXml.Attributes(XName.Get("id")).FirstOrDefault();
			Guid pid = Guid.Empty;
			
			try {
				if (providerId != null)
					pid = new Guid(providerId.Value);
				else
					throw new FormatException("Id Required");

			} catch (FormatException fex) {
				throw new Http.ClientException(Http.ClientErrorStatus.BadRequest, "Invalid Id", fex);
			}

			var spec = RequestHandlerFactory.RegisteredItemSpecifications.FirstOrDefault(p => p.Id == pid);
			// Explanation for this serialization weirdness:
			// http://www.hanselman.com/blog/MixingXmlSerializersWithXElementsAndLINQToXML.aspx
			XmlSerializer xs = new XmlSerializer(typeof(ItemSpecification));
			XDocument xd = new XDocument();
			using (XmlWriter w = xd.CreateWriter()) xs.Serialize(w, spec.ToStruct());
			XElement specElement = xd.Root;
			specElement.Remove();
			elementList.Add(specElement);
			return elementList;
		}

		/// <summary>
		/// Returns a WebDAV response containing a list of providers.
		/// </summary>
		/// <param name="item">The item which is used for the Href of the response</param>
		/// <returns>a webdav response with a listing of providers</returns>
		private static List<XElement> ProviderReferenceReport(IItemInstance item)
		{
			List<XElement> elementList = new List<XElement>();
			elementList.Add(new XElement(XName.Get("response", "DAV:"),
				new XElement(XName.Get("href", "DAV:"), item.Href.ToWebsiteUri()),
				new XElement(XName.Get("providers", "uri://MetaCollection/v2"),
					RequestHandlerFactory.RegisteredItemSpecifications.AsReferenceElements())));
			return elementList;
		}

		/// <summary>
		/// Gets the requested properties.
		/// </summary>
		/// <param name="requestXml">The request XML.</param>
		/// <returns>
		/// A collection of properties requested in the PROPFIND request
		/// </returns>
		private static List<XName> GetRequestedProperties(XElement requestXml)
		{
			List<XName> requestedProperties = new List<XName>();

			if (requestXml != null)
			{
				var propNames = requestXml.Elements(XName.Get("prop", "DAV:")).FirstOrDefault();

				if (propNames != null)
					requestedProperties = requestedProperties.Concat(from n in propNames.Elements() where n.NodeType == XmlNodeType.Element select n.Name).ToList();
			}
			return requestedProperties;
		}

		/// <summary>
		/// Gets the report request type of the request.
		/// </summary>
		/// <param name="requestXml">The request XML.</param>
		/// <returns>The Report Request Type of the REPORT request</returns>
		/// <exception cref="Http.ClientException">Throws a BadRequest exception if the
		/// request format is invalid.</exception>
		private static ReportRequestType GetRequestType(XElement requestXml)
		{
			if (requestXml != null)
			{
				switch (requestXml.Name.LocalName.ToUpperInvariant())
				{
					case "VERSION-TREE":
						return ReportRequestType.VersionTree;
					case "EXPAND-PROPERTY":
						return ReportRequestType.ExpandProperty;
					case "RESOURCE-PROVIDERS":
						return ReportRequestType.ResourceProviders;
					case "SPECIFICATIONDETAILS":
						return ReportRequestType.SpecificationDetail;
					default: return ReportRequestType.None;
				}
			}
			throw new Http.ClientException(ClientErrorStatus.BadRequest);
		}
	}

	public static class ReportExtensions
	{
		/// <summary>
		/// Returns each Item Specification as a Specification XElement.
		/// </summary>
		/// <param name="itemSpecifcations">The item specifications.</param>
		/// <returns>A set of item specification reference elements</returns>
		public static IEnumerable<XElement> AsReferenceElements(this IEnumerable<IItemSpecification> itemSpecifcations)
		{
			foreach (var i in itemSpecifcations)
			{
				yield return new XElement(XName.Get("provider", "uri://MetaCollection/v2"),
					new XAttribute("id", i.Id),
					new XAttribute("name", i.Name),
					i.Description);
			}
		}
	}
}
